/*
 * Coverage.hpp
 *
 *  Created on: Apr 22, 2009
 *      Author: Jae Hyuk Kwak
 */

#ifndef COVERAGE_HPP_
#define COVERAGE_HPP_
#include "HeteroVector.hpp"
namespace TestingCoverage {


template< typename TList, unsigned int index1 >
struct ForEachTypeVisitor;

template< typename TList >
struct ForEachTypeVisitor< TList, 0 >
{
	enum { index1 = 0 };

	template< typename FunctorT >
	ForEachTypeVisitor( FunctorT & func ) {
		func.FunctorT::template operator()< index1 >();
	}
};

template< typename TList, unsigned int index1 >
struct ForEachTypeVisitor : public ForEachTypeVisitor< TList, index1 -1 >
{
	template< typename FunctorT >
	ForEachTypeVisitor( FunctorT & func )
	: ForEachTypeVisitor< TList, index1 -1 >( func ) {
		func.FunctorT::template operator()< index1 >();
	}
};


template< typename TList, typename FunctorT >
FunctorT ForEachType( FunctorT functor ) {
	ForEachTypeVisitor< TList, Length< TList >::value - 1> forEachType( functor );
	return functor;
}

template < typename TList >
class FunctorSelectOneHeteroVector {
public:
	FunctorSelectOneHeteroVector( HeteroVector< TList > input, unsigned int targetIndex )
	: input_(input), targetIndex_(targetIndex) { }

	template< unsigned int index >
	void operator()() {
		result_.HeteroVector< TList >::template push_back< index >(
				input_.HeteroVector< TList >::template get< index >( targetIndex_ )
		);
	}

	HeteroVector< TList > getResult() { return result_; }

public:
	HeteroVector< TList > input_, result_;
	unsigned int targetIndex_;
};

template< typename TList >
HeteroVector< TList > oneHeteroVectorAt( HeteroVector< TList > several, unsigned int index ) {
	return ForEachType< TList >( FunctorSelectOneHeteroVector< TList >(several, index) ).getResult();
}

template< typename TList >
class CombineHeteroVectorFunctor {
public:
	CombineHeteroVectorFunctor( HeteroVector< TList > lhs, HeteroVector< TList > rhs )
	: lhs_(lhs), rhs_(rhs) {}

	template< unsigned int index >
	void operator()() {
		typedef typename TypeAt< TList, index >::Result EachType;
		typedef vector< EachType > VectorForEachType;
		typedef typename VectorForEachType::const_iterator VectorForEachTypeIterator;

		VectorForEachType lhsVector = lhs_.HeteroVector< TList >::template getVector< index >();
		VectorForEachTypeIterator iterLhs = lhsVector.begin();
		for(; iterLhs != lhsVector.end(); ++iterLhs)
			result_.HeteroVector< TList >::template push_back<index>( *iterLhs );

		VectorForEachType rhsVector = rhs_.HeteroVector< TList >::template getVector< index >();
		VectorForEachTypeIterator iterRhs = rhsVector.begin();
		for(; iterRhs != rhsVector.end(); ++iterRhs)
			result_.HeteroVector< TList >::template push_back<index>( *iterRhs );
	}

	HeteroVector< TList > getResult() { return result_; }

private:
	HeteroVector< TList > lhs_, rhs_, result_;
};

template< typename TList >
HeteroVector< TList > combineHeteroVector( HeteroVector< TList > lhs, HeteroVector< TList > rhs ) {
	return ForEachType< TList >(CombineHeteroVectorFunctor< TList >(lhs, rhs)).getResult();
}


template< typename TList >
struct CombinationFunctor {

	CombinationFunctor( HeteroVector< TList > input) : input_(input) {}

	template< unsigned int index1 >
	void operator()() {
		typedef typename TypeAt< TList, index1 >::Result EachType;
		typedef vector< EachType > VectorForEachType;
		typedef typename VectorForEachType::const_iterator VectorForEachTypeIterator;

		HeteroVector< TList > newResult;

		VectorForEachType additions = input_.HeteroVector< TList >::template getVector< index1 >();
		VectorForEachTypeIterator iterAddition = additions.begin();
		for(; iterAddition != additions.end(); ++iterAddition) {
			// 1. copy
			HeteroVector< TList > temp = result_;

			// 2. append a new value
			const unsigned int sizeOfOriginal = result_.HeteroVector< TList >::template size< 0 >();
			if( 0 == sizeOfOriginal ) {
				temp.HeteroVector< TList >::template push_back< index1 >( *iterAddition );
			} else {
				for(unsigned int indexAppend = 0; indexAppend < sizeOfOriginal; ++indexAppend) {
					temp.HeteroVector< TList >::template push_back< index1 >( *iterAddition );
				}
			}

			// 3. append to the new result
			newResult = combineHeteroVector( newResult, temp );
		}

		result_ = newResult;
	}

	HeteroVector< TList > getResult() { return result_; }

private:
	HeteroVector< TList > input_, result_;
};


template< typename TList >
HeteroVector< TList > allCombinationsCoverage( HeteroVector< TList > inputs ) {
	return ForEachType< TList >(CombinationFunctor< TList >(inputs)).getResult();
}

template < typename TList >
class SingleBaseChoicePolicy {
public:
	static HeteroVector< TList > selectBaseValues( const HeteroVector< TList > input ) {
		return oneHeteroVectorAt( input, 0 );
	}
};

template< typename TList, typename BaseChoicePolicy = SingleBaseChoicePolicy< TList > >
struct BaseChoiceFunctor {

	BaseChoiceFunctor( HeteroVector< TList > input) : input_(input) {
		HeteroVector< TList > baseValues = BaseChoicePolicy::selectBaseValues( input_ );
		baseChoices_ = allCombinationsCoverage( baseValues );
		numberOfBaseChoices_
			= baseChoices_.HeteroVector< TList >::template size< 0 >();

		result_ = baseChoices_;
	}

	template< unsigned int index1 >
	void operator()() {
		typedef typename TypeAt< TList, index1 >::Result EachType;
		typedef vector< EachType > VectorForEachType;
		typedef typename VectorForEachType::const_iterator VectorForEachTypeIterator;

		HeteroVector< TList > newResult = result_;

		for(unsigned int indexForBaseChoices = 0; indexForBaseChoices < numberOfBaseChoices_; ++ indexForBaseChoices) {

			// 1. choose a set of base values
			HeteroVector< TList > aBaseChoice = oneHeteroVectorAt( baseChoices_, indexForBaseChoices );

			VectorForEachType variations = input_.HeteroVector< TList >::template getVector< index1 >();
			VectorForEachTypeIterator iterVariation = variations.begin();
			for(; iterVariation != variations.end(); ++iterVariation) {

				// 2. change only one value
				EachType newValue = *iterVariation;
				const EachType baseValue = aBaseChoice.HeteroVector< TList >::template get< index1 >( 0 );
				if( newValue == baseValue ) continue;
				aBaseChoice.HeteroVector< TList >::template replace< index1 >( 0, newValue );

				// 3. append to the new result
				newResult = combineHeteroVector( newResult, aBaseChoice );
			}
		}

		result_ = newResult;
	}

	HeteroVector< TList > getResult() { return result_; }

private:
	HeteroVector< TList > input_, baseChoices_, result_;
	unsigned int numberOfBaseChoices_;
};

template< typename TList >
HeteroVector< TList > baseChoiceCoverage( HeteroVector< TList > inputs ) {
	return ForEachType< TList >(BaseChoiceFunctor< TList >(inputs)).getResult();
}


// TODO
//eachChoiceCoverage
//pairWiseCoverage
//TWiseCoverage
//multiBaseChoicesCoverage

}


#endif /* COVERAGE_HPP_ */
